package com.gusto.engine.recommend.prediction.base;

import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.gusto.engine.colfil.Distance;
import com.gusto.engine.colfil.Evaluation;
import com.gusto.engine.colfil.Prediction;
import com.gusto.engine.colfil.Rating;
import com.gusto.engine.recommend.PredictionService;
import com.gusto.engine.recommend.prediction.base.BaseImpl;

/**
 * <p>Basic implementation for Semantic algorithms.<p>
 * 
 * @see BaseCollaborativeImpl
 * @see BaseSemanticImpl
 * @see BaseHybridImpl
 * 
 * @author amokrane.belloui@gmail.com
 *
 */
public abstract class BaseSemanticImpl extends BaseImpl implements PredictionService {
	
	private Logger log = Logger.getLogger(getClass());
	
	protected Integer minEvals = 1;
	public void setMinEvals(Integer minEvals) {
		this.minEvals = minEvals;
	}
	
	protected Integer userMaxNeighbors, itemMaxNeighbors;
	public void setUserMaxNeighbors(Integer userMaxNeighbors) {
		this.userMaxNeighbors = userMaxNeighbors;
	}
	public void setItemMaxNeighbors(Integer itemMaxNeighbors) {
		this.itemMaxNeighbors = itemMaxNeighbors;
	}
	
	protected Double userMinSimilarity, itemMinSimilarity;
	public void setUserMinSimilarity(Double userMinSimilarity) {
		this.userMinSimilarity = userMinSimilarity;
	}
	public void setItemMinSimilarity(Double itemMinSimilarity) {
		this.itemMinSimilarity = itemMinSimilarity;
	}
	
	public Prediction predict(long userId, long itemId, boolean includePrediction) throws Exception {
		long start = System.currentTimeMillis();
		
		// Calculate Value
		List<Distance> usersSem = semanticNeighborhoodDelegate.getNearUsers(userId, userMaxNeighbors, userMinSimilarity);
		List<Distance> itemsSem = semanticNeighborhoodDelegate.getNearItems(itemId, itemMaxNeighbors, itemMinSimilarity);
		
		Map<Long, Double> usersWeightsSem = this.buildUserWeights(userId, usersSem);
		Map<Long, Double> itemsWeightsSem = this.buildItemWeights(itemId, itemsSem);
		
		log.debug("Getting submatrix for " + usersWeightsSem + " " + itemsWeightsSem);
		List<Rating> evalsSem = collaborativeService.getSubMatrix(usersWeightsSem.keySet(), itemsWeightsSem.keySet());
		log.debug("Submatrix contains " + evalsSem.size() + " ratings");
				
		Double user_mean = collaborativeService.getUserMeanRating(userId);
		Double item_mean = collaborativeService.getItemMeanRating(itemId);
		Double val = null;
		
		if (usersSem.size() - usersWeightsSem.size() > 1) {
			log.error("Error User " + userId + " " + itemId + " [" + usersSem.size() + " -> to " + usersWeightsSem.size() + "]");
		}
		if (itemsSem.size() - itemsWeightsSem.size() > 1) {
			log.error("Error Item " + userId + " " + itemId + " [" + itemsSem.size() + " -> to " + itemsWeightsSem.size() + "]");
		}

		val = doPrediction(evalsSem, user_mean, item_mean, usersWeightsSem, itemsWeightsSem);
		
		logPrediction(0, 0, 0, (System.currentTimeMillis() - start), "PPPP");
		return returnPrediction(userId, itemId, val);
	}
	
	protected abstract Double doPrediction(List<? extends Evaluation> evalsSem, double user_mean, double item_mean, Map<Long, Double> usersWeightsSem, Map<Long, Double> itemsWeightsSem);
	
}
